A note to random people who find this on the Web: this "course" was never meant to be more than a thumbnail introduction to C to supplement some informal lectures written in a hurry to try and give our graduate students (who mostly know FORTRAN) some knowledge of C. It is not superbly written, and the HTML leaves a lot to be desired. At the time (1994) there wasn't much available on the Web, hopefully some decent C courses have appeared by now. I hope that you find something useful here. Sometime I may get a chance to improve it...
Wednesdays, 4pm, in Room 112. Written by Michael Ashley.
So many instructions were removed in going from BCPL to B, that Dennis Ritchie of Bell Labs put some back in (in 1972), and called the language C.
The famous book The C Programming Language was written by Kernighan and Ritchie in 1978, and was the definitive reference book on C for almost a decade.
The original C was still too limiting, and not standardized, and so in 1983 an ANSI committee was established to formalise the language definition.
It has taken until now (ten years later) for the ANSI standard to become well accepted and almost universally supported by compilers.
Note also the strange character sequence `\n' in the string constant "hello world\n". `\n' is called a newline character, and is regarded as a single character in C (i.e., it occupies one byte of storage).
The value of a character constant is the numeric value of the character in the computer's character set (e.g., 'A' has the value 65). In 99.99% of cases this is the ASCII character set, but this is not defined by the standard!
But how do you represent a character such as a single quote itself? The answer is to use an escape sequence.
For reference, here is a program to print out all the special escape sequences:
audible alert (bell) BEL \a 7 backspace BS \b 8 horizontal tab HT \t 9 newline LF \n 10 vertical tab VT \v 11 formfeed FF \f 12 carriage return CR \r 13 double quote " \" 34 single quote ' \' 39 question mark ? \? 63 backslash \ \\ 92Note: this program actually produces the wrong output when used with cc on newt!
In addition, you can specify any 8-bit ASCII character using either \ooo or \xhh where `ooo' is an octal number (with from 1 to 3 digits), and 'xhh' is a hexadecimal number (with 1 or 2 digits). For example, \x20 is the ASCII character for SPACE.
The above program also shows how to add comments to your C program.
String constants are a sequence of zero or more characters, enclosed in double quotes. For example, "test", "", "this is an invalid string" are all valid strings (you can't always believe what a string tells you!). String constants are stored in memory as a sequence of numbers (usually from the ASCII character set), and are terminated by a null byte (\0). So, "test" would appear in memory as the numbers 116, 110, 115, 116, 0.
We have already used some examples of strings in our programs, e.g, "hello world\n" was a null-terminated character string that we sent to the printf function above.
The idea behind make is that you should be able to compile/link a program simply by typing make (followed by a carriage-return). For this to work, you have to tell the computer how to build your program, by storing instructions in a Makefile.
While this step takes some time, you are amply repaid by the time savings later on. make is particularly useful for large programs that consist of numerous source files: make only recompiles the file that need recompiling (it works out which ones to process by looking at the dates of last modification).
Here is an example of a simple Makefile:
cd mkdir hello cd hello cat > hello.c #include <stdio.h> main () { printf ("hello world\n"); } ^D cat > Makefile hello: hello.o; cc hello.o -o hello ^D make ./hello
char 1 bytes -128 to 127 unsigned char 1 bytes 0 to 255 short 2 bytes -32768 to 32767 unsigned short 2 bytes 0 to 65535 int 4 bytes -2147483648 to 2147483647 unsigned int 4 bytes 0 to 4294967295 long 4 bytes -2147483648 to 2147483647 unsigned long 4 bytes 0 to 4294967295 float 4 bytes 1.175494e-38 to 3.402823e+38 double 8 bytes 2.225074e-308 to 1.797693e+308 precision of float 6 digits precision of double 15 digits
int 123, -1, 2147483647, 040 (octal), 0xab (hexadecimal) unsigned int 123u, 2147483648, 040U (octal), 0X02 (hexadecimal) long 123L, 0XFFFFl (hexadecimal) unsigned long 123ul, 0777UL (octal) float 1.23F, 3.14e+0f double 1.23, 2.718281828 long double 1.23L, 9.99E-9LNote:
When compiled with 'f77' on newt, this gives the result '4.7683715864721423E-08', not zero as you might expect. The reason for this is that '1.0' and '0.2' are single precision numbers by default, and so the addition is only done to this precision. The number '1.2' is converted into binary with double precision accuracy, and so is a different number from '1.0 + 0.2'.
Interestingly, the above program gives the result '0.0000000000000000E+00' when compiled with 'f772.1' on newt, and '4.768371586472142E-08' when compiled with 'f77' on the CANCES HP cluster.
The correct way to write this program is as follows:
Here is the equivalent program written in C:
In this case the result is '0.0000000000000000e+00', but this isn't really a fair comparison with our original FORTRAN program since floating point numbers are assumed to be 'double' in C, not 'real*4' as in FORTRAN. So let's go back to using 'float's instead:
Now the program generates '-4.4703483581542969e-08' when compiled with 'cc' on newt, and yet it gives '0.0000000000000000e+00' when compiled with 'gcc', interesting...
The lesson to be learnt here is when writing constants, always think carefully about what type you want them to be, and use the suffixes 'U', 'L', and 'F' to be explicit about it. It is not a good idea to rely on the compiler to do what you expect. Don't be surprised if different machines give different answers if you program sloppily.
This program produces the result 'i = 1; j = -1'. Note that the floating point numbers have been truncated when converted to integers (FORTRAN does the same thing).
When converting integers to floating-point, be aware that a 'float' has fewer digits of precision than an 'int', even though they both use 4 bytes of storage (on newt). This can result in some strange behaviour, e.g.,
This program produces the following output when compiled with 'cc':
4294967295 4.2949672960000e+09 1.0000000000000e+00
and this output when compiled with 'gcc':
4294967295 4.2949672960000e+09 0.0000000000000e+00
Curiouser and curiouser... It appears that what is happening is that 'cc' is doing the calculation 'f - i' as a 'double', i.e., 'f' and 'i' are converted to type 'double', and then subtracted. Whereas 'gcc' is converting 'i' to type 'float' (just as was done with 'f = i'), and hence the subtraction results in zero. To test this hypothesis, you can force 'cc' to use a 'float' conversion by putting a type-cast operator before 'i'. Here it is
This program now gives the same results when used with 'cc' or 'gcc' (i.e., zero). Incidentally, 'gcc's behaviour without the '(float)' agrees with the ANSI standard.
Note the use of the type-cast operator '(float)'. This converts the number or variable or parethesised expression immediately to its right, to the indicated type. It is a good idea to use type-casting to ensure that you leave nothing to chance.
Some of the operators we have already seen (e.g., 'sizeof()'), others are very simple (e.g., +, -), others are really neat (e.g., ~, !), others are useful for adding/subtracting 1 automatically (e.g., ++i, --i, i++, i--), and the rest involve pointers and addressing, which will be covered in detail later.
sizeof(i) the number of bytes of storage allocated to i +123 positive 123 -123 negative 123 ~i one's complement (bitwise complement) !i logical negation (i.e., 1 if i is zero, 0 otherwise) *i returns the value stored at the address pointed to by i &i returns the address in memory of i ++i adds one to i, and returns the new value of i --i subtracts one from i, and returns the new value of i i++ adds one to i, and returns the old value of i i-- subtracts one from i, and returns the old value of i i[j] array indexing i (j) calling the function i with argument j i.j returns member j of structure i i->j returns member j of structure pointed to by i
Here is a list. All the usual operators that you would expect are there, with a whole bunch of interesting new ones.
+ addition - subtraction * multiplication / division % remainder (e.g., 2%3 is 2), also called 'modulo' << left-shift (e.g., i<<j is i shifted to the left by j bits) >> right-shift & bitwise AND | bitwise OR ^ bitwise exclusive-OR && logical AND (returns 1 if both operands are non-zero; else 0) || logical OR (returns 1 if either operand is non-zero; else 0) < less than (e.g., i<j returns 1 iff i is less than j) > greater than <= less than or equal >= greater than or equal == equals != does not equal ? conditional operator, explained later...Note:
= assignment += addition assignment -= subtraction assignment *= multiplication assignment /= division assignment %= remainder/modulus assignment &= bitwise AND assignment |= bitwise OR assignment ^= bitwise exclusive OR assignment <<= left shift assignment >>= right shift assignment
So, for example, 'i += j' is equivalent to 'i = i + j'. The advantage of the assignment operators is that they can reduce the amount of typing that you have to do, and make the meaning clearer. This is particularly noticeable when, instead of a simple variable such as 'i', you have something complicated such as 'position[wavelength + correction_factor * 2]';
The thing to the left of the assignment operator has to be something where a result can be stored, and is known as an 'lvalue' (i.e., something that can be on the left of an '='). Valid 'lvalues' include variables such as 'i', and array expressions. It doesn't make sense, however, to use constants or expressions to the left of an equals sign, so these are not 'lvalues'.
For example,
Example Equivalent to ------- ------------- i = ((j = 2), 3); i = 3; j = 2; myfunct (i, (j = 2, j + 1), 1); j = 2; myfunct (i, 3, 1);
The comma operator has the lowest precedence, so it is always executed last when evaluating an expression. Note that in the example given comma is used in two distinct ways inside an argument list for a function.
Both the above examples are artifical, and not very useful. The comma operator can be useful when used in 'for' and 'while' loops as we will see later.
The precedence of an operator gives the order in which operators are applied in expressions: the highest precedence operator is applied first, followed by the next highest, and so on.
The associativity of an operator gives the order in which expressions involving operators of the same precedence are evaluated.
The following table lists all the operators, in order of precedence, with their associativity:
Operator Associativity -------- ------------- () [] ->> . left-to-right - + ++ -- ! ~ * & sizeof (type) right-to-left * / % left-to-right + - left-to-right << >> left-to-right < <= > >= left-to-right == != left-to-right & left-to-right ^ left-to-right | left-to-right && left-to-right || left-to-right ?: right-to-left = += -= *= /= %= &= ^= |= <<= >>= right-to-left , left-to-right
Note: the + - and * operators appear twice in the above table. The unary forms (on the second line) have higher precedence that the binary forms.
Operators on the same line have the same precedence, and are evaluated in the order given by the associativity.
To specify a different order of evaluation you can use parentheses. In fact, it is often good practice to use parentheses to guard against making mistakes in difficult cases, or to make your meaning clear.
It is possible to write C expressions that give different answers on different machines, since some aspects of expression-evaluation are not defined by the ANSI standard. This is deliberate since it gives the compiler writers the ability to choose different evaluation orders depending on the underlying machine architecture. You, the programmer, should avoid writing expressions with side effects.
Here are some examples:
myfunc (j, ++j); /* the arguments may be the same, or differ by one */ array[j] = j++; /* is j incremented before being used as an index? */ i = f1() + f2(); /* the order of evaluation of the two functions is not defined. If one function affects the results of the other, then side effects will result */
A useful aspect of C is that it guarantees the order of evaluation of expressions containing the logical AND (&&) and OR (||) operators: it is always left-to-right, and stops when the outcome is known. For example, in the expression `1 || f()', the function `f()' will not be called since the truth of the expression is known regardless of the value returned by `f()'.
It is worth keeping this in mind. You can often speed up programs by rearranging logical tests so that the outcome of the test can be predicted as soon as possible.
Another good example is an expression such as `i >= 0 && i <n && array[i] == 0'. The compiler will guarantee that the index into `array' is within legal bounds (assuming the array has `n' elements).
auto double int struct break else long switch case enum register typedef char extern return union const float short unsigned continue for signed void default goto sizeof volatile do if static while
Prototypes for the math functions are in the system include-file "math.h", so you should put the line
#include <math.h>in any C source file that calls one of them.
Here is a list of the math functions defined by the ANI standard:
sin(x) sine of x cos(x) cosine of x tan(x) tan of x asin(x) arcsine of x, result between -pi/2 and +pi/2 acos(x) arccosine of x, result between 0 and +pi atan(x) arctan of x, result between -pi/2 and +pi/2 atan2(y,x) arctan of (y/x), result between -pi and +pi hsin(x) hyperbolic sine of x hcos(x) hyperbolic cosine of x htan(x) hyperbolic tan of x exp(x) exponential function log(x) natural logarithm log10(x) logarithm to base 10 pow(x,y) x to the power of y (x**y in FORTRAN) sqrt(x) the square root of x (x must not be negative) ceil(x) ceiling; the smallest integer not less than x floor(x) floor; the largest integer not greater than x fabs(x) absolute value of x ldexp(x,n) x times 2**n frexp(x, int *exp) returns x normalized between 0.5 and 1; the exponent of 2 is in *exp modf(x, double *ip) returns the fractional part of x; the integral part goes to *ip fmod(x,y) returns the floating-point remainder of x/y, with the sign of xIn the above table, 'x', and 'y' are of type 'double', and 'n' is an 'int'. All the above functions return 'double' results.
C libraries may also include 'float' versions of the above. For example, 'fsin(x)' on newt takes a float argument and returns a float result. Microsoft C does not provide 'float' versions (presumably because the floating-point accelerator chips do all their work in double precision).
The basic looping construct in C is the `for' loop. It is analagous to the `do' loop in FORTRAN, although it is considerably more flexible.
Here is the syntax of the `for' statement:
for (initial_expression; loop_condition; loop_expression) statement;
An example will clear this up:
for (i = 0; i < 100; i++) printf ("%i\n", i);
which simply prints the first 100 integers onto `stdout'. If you want to include more that one statement in the loop, use curly brackets to delimit the body of the loop, e.g.,
for (i = 0; i < 100; i++) { j = i * i; printf ("i = %i; j = %i\n", i, j); }
cc -o prog prog.c -lm
The `-l' switch stands for `library', which means that the specified library of pre-compiled C routines is searched in order to satisfy any external references from yoru program `prog.c'. The library that is searched in this case is `libm.a' and the path that is used for the search is the default library search path, which include `/usr/lib' where 'libm.a' is found.
To use a library in a directory that is not part of the default library search path, you use the '-L' switch. For example, to search the library '/usr/users/smith/libastro.a', you would use
cc -o prog prog.c -L/usr/users/smith -lastro
Note: the order of the switches is important. External references are only searched for in libraries to the right of the reference. So, if you have two libraries that call each other, then you need to do something like the following:
cc -o prog prog.c -L/usr/users/smith -llib1 -llib2 -llib1
Here is a simple example of calling the math library:
This program produces the result:
The sine of 1.570796 is 1.000000
However, if you leave off the `#include <math.h>' line, you will get
The sine of 1.570796 is 4.000000
Why, because the default type for an undefined function is `extern int function();'
The last statement in the body of statements in a `for' loop (or, in fact, in any other compound statement) must be terminated with a semicolon.
For example,
for (i = 0; i < 10; i++) { x = i * i; x += 2; /* the semicolon is required here */ } /* do not use a semicolon here */
The example programs I showed last time didn't always have
`#include You can create variables that are local to a compound statement
by declaring the variables immediately after the leading curly bracket.
Variables in C belong to one of two fundamental storage classes:
`static' or `automatic'.
A static variable is stored at a fixed memory location in the computer,
and is created and initialised once when the program is first started.
Such a variable maintains its value between calls to the block (a
function, or compound statement) in which it is defined.
An automatic variable is created, and initialised, each time the
block is entered (if you jump in half-way through a block, the creation still
works, but the variable is not initialised). The variable is destroyed when
the block is exited.
Variables can be explicitly declared as `static' or `auto' by using these
keywords before the data-type definition. If you don't use one of these
keywords, the default is `static' for variables defined outside any
block, and `auto' for those inside a block.
Actually, there is another storage class: `register'. This is like `auto'
except that it asks the compiler to try and store the variable in one of
the CPU's fast internal registers. In practice, it is usually best not
to use the `register' type since compilers are now so smart that they can
do a better job of deciding which variables to place in fast storage
than you can.
Variables can be qualified as `const' to indicate that they are
really constants, that can be initialised, but not altered (analagous
to the FORTRAN PARAMETER statement).
Variables can also be termed `volatile' to indicate that their
value may change unexpectedly during the execution of the program
(e.g., they may be hardware registers on a PC, able to be altered by
external events). By using the `volatile' qualifier, you prevent the
compiler from optimising the variable out of loops.
Variables (and functions) can also be classified as `extern', which
means that they are defined external to the current block (or even to
the current source file). An `extern' variable must be defined once
(and only once) without the `extern' qualifier.
As an example of an `extern' function, all the functions in
`libm.a' (the math library) are external to the source file that calls them.
A variable is initialised by equating it to a constant expression
on the line in which it is defined. For example
`static' variables are initialised once (to zero if not
explicitly initialised), `automatic' variables are
initialised when the block in which they are defined is entered
(and to an undefined value if not explicitly initialised).
The `constant expression' can contain combinations of any type of constant,
and any operator (except assignment, incrementing, decrementing,
function call, or the comma operator), including the ability to use
the unary & operator to find the address of static variables.
Here are some valid examples:
Notes:
Where `statement' is a simple C statement ending in a semicolon, or
a compound statement ending in a curly bracket.
Some examples will help:
`break' exits the innermost current loop.
`continue' starts the next iteration of the loop.
Infinite loops can be useful. They are normally terminated using a
conditional test with a `break' or `return' statement.
C does have a `goto' statement, but you don't need it. Using `goto'
is almost always a result of bad programming.
The format string given to the `printf' function may contain both
ordinary characters (which are simply printed out) and conversion
characters (beginning with a percent symbol, %, these define how the
value of an internal variable is to be converted into a character
string for output).
Here is the syntax of a conversion specification:
Here is an example program to show some of these effects:
Notes:
For example:
scanf is actually an integer function, which returns the number
of input items assigned (or EOF if the end-of-file is reached or an
error occurred).
The ampersand character `&' is a unary operator that returns the
address of the thing to its right. Recall that a C function can not
alter the value of its arguments (but there is nothing stopping it
altering that value that is pointed to by one of its arguments!).
Arrays are declared in C as follows (for example):
In this example, `count' is an array that can hold 100 integers,
and `temperature' is an array that can hold 1024 floats.
So far so good. The major departure from FORTRAN is that the
first element in a C array is element number 0 (rather than 1 as
in FORTRAN). While this may be confusing to die-hard FORTRAN
programmers, it is really a more natural choice. A side-effect of
this choice is that the last element in an array has an index
that is one less that the declared size of the array. This
is a source of some confusion, and something to watch out for.
To initialise an array, specify the initial values in a list
within curly brackets. For example:
In this example, `primes' is initialised with the values of the first
100 primes (check them!), and the first two elements (temp[0] and temp[1])
of `temp' are initialised to 5.0F and 2.3F respectively. The remaining
elements of `temp' are set to 0.0F. Note that we use the trailing `F'
on these numbers to indicate that they are floats, not doubles.
The array `trouble' in the above example contains three double
numbers. Note that its length is not explicitly declared, C is smart
enough to work the length out.
Static arrays that are not explicitly initialised are set to
zero. Automatic arrays that are not explicitly initialised, have
undefined values.
Unfortunately, C does not have any shorthands like those in
FORTRAN for initialising vast slabs of memory with distinct numbers.
This is probably best done with a `for' loop at run-time.
Multidimensional arrays are declared and referenced in C by using
multiple sets of square brackets. For example,
`table' is a 2x3x4 array, with the rightmost array subscript changing
most rapidly as you move through memory (the first element is
`table[0][0][0]', the next element is `table[0][0][1]', and the
last element is `table[1][2][3]'.
This previous declaration is equivalent to the following FORTRAN
declaration,
When writing programs that use huge arrays (say, more than
a few megabytes), you should be very careful to ensure that array
references are as close as possible to being consecutive (otherwise
you may get severe swapping problems).
This is best demonstrated with an example:
Note that I have only initialised a 3x3 subset of the 3x4 array.
The last column of each row will have the default initialisation of zero.
Arrays can be of any type. Character arrays hold a single
character in each element. There is nothing analogous to the FORTRAN
CHARACTER type, which has special purpose operators to support it.
In C, you manipulate character strings as arrays of characters, and
operations on the strings (such as concatenation, searching) are done
by calling special libary functions (e.g., strcat, strcmp).
Note that when calling a string-manipulation function, the end of the
string is taken as the position of the first NUL character (0) in the
string. This is quite different from FORTRAN, which independently stores
the length of each character string.
Character arrays can be initialised in the following ways:
In the example, `str' has length 3 bytes, and `prompt' has
length 22 bytes, which is one more than the number of characters in
"please enter a number", the extra character is used to store a NUL
character (zero) as an indication of the end of the string. `str' does
not having a trailing NUL.
If there is one thing that sets C apart from most other languages,
it is the use of pointers. A `pointer' is a variable containing the
address of a memory location.
Suppose `p' is a pointer, then `*p' is the thing which `p' points to.
Suppose `i' is being pointed at by `p', then `i' and `*p' are
the same thing, and `p', being equal to the address of `i', is equal
to `&i' (remember, `&' is the unary address operator).
Pointers are declared by specifying the type of thing they point
at. For example,
defines `p' as a pointer to an int (so, therefore, `*p' is an int, hence
the form of the declaration).
Note carefully, then by declaring `p' as in the above example, the
compiler simply allocates space for the pointer (4 bytes in most cases),
not for the variable that the pointer points to! This is a very
important point, and is often overlooked. Before using `*p' in an
expression, you have to ensure that `p' is set to point to a valid
int. This `int' must have had space allocated for it, either statically,
automatically, or by dynamically allocating memory at run-time (using
a `malloc' function).
Here is an example showing some of the uses of pointers:
Note the very important point that the name of an array (`msg'
in the above example), if used without an index, is considered
to be a pointer to the first element of the array. In fact,
an array name followed by an index is exactly equivalent to
a pointer followed by an offset. For example,
Note, however, that `cp' is a variable, and can be changed, whereas
`msg' is a constant, and is not an lvalue.
We have already seen that C functions can not alter their
arguments. They can, however, alter the variables that their arguments
point to. Hence, by passing a pointer to a variable, one can get the
effect of `call by reference' in FORTRAN.
Here is an example of a function that swaps the values of
its two arguments:
If all the asterisks were left out of this routine, then it would
still compile OK, but its only effect would be to swap local copies
of `pi' and `pj' around.
The standard C libraries include a bunch of functions for manipulating
strings (i.e., arrays of chars).
Note that before using any of these functions, you should include the
line "#include The following program illustrates the use of `strtok', the most complex
of the string functions.
<stdio> defines a number of functions that are used for accessing files.
Before using a file, you have to declare a pointer to it, so that it can
be referred to with the functions. You do this with, for example,
where `FILE' is a type defined in <stdio> (it is usually a complicated
structure of some sort).
To open a file, you do, for example:
Note the use of "r" to indicate read access, and "w" to indicate
write access. The following modes are available:
Once the file is opened, you can use the following functions to
read/write it:
When you have finished with a file, you should close it with 'fclose':
It can also be useful to flush any buffers associated with a
file, to guarantee that the characters that you have written have actually
been sent to the file:
To check the status of a file, the following functions can be called:
Note that the above `functions' are actually defined as pre-processor
macros in C. This is quite a common thing to do.
Any programming language worth its salt has to allow you to manipulate more
complex data types that simply ints, and arrays. You need to have structures
of some sort. This is best illustrated by example:
To define a structure, use the following syntax:
This defines a new data type, called `time', which contains
3 elements (packed consecutively in memory). To declare a variable of
type `time', do the following:
To refer to an individual element of a structure, you use the following
syntax:
Structures can contain any type, including arrays and other
structures. A common use is to create a linked list by having a structure
contain a pointer to a structure of the same type, for example,
As an example of how to program in C, let's explore the topic
of multiple precision arithmetic. All the hard work will be done by
GNU's Multiple Precision Arithmetic Library (GNU MP), written by
Torbjorn Granlund. The version I will be using here is 1.3.2, obtained
from archie.au on 9 August 1994.
Multiple precision arithmetic allows you to perform calculations
to a greater precision than the host computer will normally allow.
The penalty you pay is that the operations are slower, and that you
have to call subroutines to do all the calculations.
GNU MP can perform operations on integers and rational numbers.
It uses preprocessor macros (defined in gmp.h) to define special data
types for storing these numbers. MP_INT is an integer, and MP_RAT is a
rational number. However, since a multiple precision number may occupy
an arbitrarily large amount of memory, it is not sufficient to allocate
memory for each number at compile time. GNU MP copes with this problem
by dynamically allocating more memory when necessary.
To begin with you need to initialise each variable that you are
going to use. When you are finished using a variable, you should
free the space it uses by calling a special function.
Let's now try a full program. For example, calculating the square
root of two to about 200 decimal places:
To compile, link, and run the program, use
Write a C program that will accept an arbitrarily long list of real
numbers from stdin, one per line, and will output the list sorted into
ascending order. You should use "malloc" to obtain space to store the
numbers. You do not know in advance how many numbers to expect, so you
will have to "malloc" on-the-fly as necessary.
In addition, you will each be assigned one of the following
exercises:
It should return
You are not allowed to use the C library function for reversing strings!
It should return
It should return
Variables defined within compound statements
Variable storage classes
Const - volatile
Extern
An example showing storage class and variable scope
Initialisation of variables
int i = 0;
If statements
if (expression)
statement
else if (expression)
statement
else if (expression)
statement
else
statement
if (i == 6) x = 3;
if (i == 6) {
x = 3;
}
if (i) x = 3;
if (i)
x = 3;
else if (i ==1)
if (j)
y = 2;
else /* NOTE: possible ambiguity here, the compiler uses */
y = 3; /* the closest if statement that does not have */
else { /* an else clause */
x = 4;
y = 5;
}
Break and continue
These two statements are used in loop control.
Infinite loops
for (;;;) {
statement;
}
while (1) {
statement;
}
do {
statement;
} while (1);
goto
Formatted output: printf description
%{flags: - + space 0 #}{minimum field width}{.}{precision}{length modifier}{conversion character}
Character Type Result
d,i int signed decimal integer
o int unsigned octal (no leading zero)
x, X int unsigned hex (no leading 0x or 0X)
u int unsigned decimal integer
c int single character
s char * characters from a string
f double floating point [-]dddd.pppp
e, E double exponential [-]dddd.pppp e[=/-]xx
g, G double floating is exponent less than -4, or >= precision
else exponential
p void * pointer
n int * the number of characters written so far by printf
is stored into the argument (i.e., not printed)
% print %
Formatted input: scanf description
Input in C is similar to output: the same conversion
characters are used. The main difference is that you
use a routine called `scanf', and you must pass the addresses
of the variables you want to change, not their values.
scanf ("%d", &i); /* reads an integer into `i' */
scanf ("%i", &i); /* reads an integer (or octal, or hex) into `i' */
scanf ("%f %i", &f, &i); /* reads a double followed by an integer */
User input, a real program example
EXERCISE: write a program in C to make a
nicely-formatted table of sines, cosines, and tangents, of all the
integral degree values between two numbers entered by the user.
The on-line manual pages
man, xman, dxbook.
The simple debugger `dbx'
cc -g -O0 main.c
dbx a.out
quit[!] - quit dbx
run arg1 arg2 ... { f1 }& f2 - begin execution of the program
stop at {line} - suspend execution at the line
[n] cont {signal} - continue with signal
return - continue until the current procedure returns
print {exp} ... - print the value of the expressions
printf "string", exp, ... - print expressions using format string(C)
where [n] - print currently active procedures (stack trace)
status - print trace/stop/record's in effect
func {proc} - move to activation level of {proc}
{exp}[/ | ?]{count}{format} - display count number of formatted memory items
file {file} - change current file to file
list {exp1},{exp2} - list source lines from {exp1} to {exp2}
list {exp}:{int} - list source lines at {exp} for {int} lines
sh {shell command} - perform shell command
Arrays
int counts[100];
float temperature[1024];
Initialising arrays
int primes[100] =
{2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61,
67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139,
149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223,
227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293,
307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383,
389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463,
467, 479, 487, 491, 499, 503, 509, 521, 523, 541};
float temp[1024] = {5.0F, 2.3F};
double trouble[] = {1.0, 2.0, 3.0};
Multidimensional arrays
int table[2][3][4];
INTEGER*4 TABLE(4,3,2)
Initialisation of multidimensional arrays
int mat[3][4] = {
{ 0, 1, 2},
{ 3, 4, 5},
{ 6, 7, 8}
}
Character arrays
char str[] = {'a', 'b', 'c'};
char prompt[] = "please enter a number";
Pointers
int *p;
#include
#include
Pointers used as arguments to functions
void swap_args (int *pi, int *pj) {
int temp;
temp = *pi;
*pi = *pj;
*pj = temp;
}
String functions
char *strcat (s1, s2) Concatenates s2 onto s1. null-terminates s1.
Returns s1.
char *strchr (s, c) Searches string s for character c. Returns a
pointer to the first occurence, or null pointer
if not.
int strcmp (s1, s2) Compares strings s1 and s2. Returns an integer
that is less than 0 is s1 < s2; zero if s1 = s2;
and greater than zero is s1 > s2.
char *strcpy (s1, s2) Copies s2 to s1, returning s1.
size_t strlen (s) Returns the number of characters in s, excluding
the terminating null.
char *strncat (s1, s2, n) Concatenates s2 onto s1, stopping after `n'
characters or the end of s2, whichever occurs
first. Returns s1.
int strncmp (s1, s2, n) Like strcmp, except at most `n' characters are
compared.
char *strncpy (s1, s2) Like strcpy, except at most `n' characters are
copied.
char *strrchr (s, c) Like strchr, except searches from the end of
the string.
int strstr (s1, s2) Searches for the string s2 in s1. Returns a
pointer if found, otherwise the null-pointer.
size_r strspn (s1, s2) Returns the number of consecutive characters
in s1, starting at the beginning of the string,
that are contained within s2.
size_r strcspn (s1, s2) Returns the number of consecutive characters
in s1, starting at the beginning of the string,
that are not contained within s2.
char *strpbrk (s1, s2) Returns a pointer to the first character in s1
that is present in s2 (or NULL if none).
char *strerror (n) Returns a pointer to a string describing the
error code `n'.
char *strtok (s1, s2) Searches s1 for tokens delimited by characters
from s1. The first time it is called with a
non-null s1, it writes a NULL into s1 at the
first position of a character from s2, and
returns a pointer to the beginning of s1. When
strtok is then called with a null s1, it finds
the next token in s1, starting at one position
beyond the previous null.
File operations
FILE *in_file;
FILE *out_file;
in_file = fopen ("input_file.dat", "r");
out_file = fopen ("output_file.dat", "w");
"r" read
"w" write (destroys any existing file with the same name)
"rb" read a binary file
"wb" write a binary file (overwriting any existing file)
"r+" opens an existing file for random read access, and
writing to its end
"w+" opens a new file for random read access, and writing to
its end (destroys any existing file with the same name)
int getc (FILE *fp) Returns the next character from `fp', or
EOF on error or end-of-file.
int putc (int c, FILE *fp) Write the character c to the file `fp',
returning the character written, or EOF on
error.
int fscanf (FILE *fp, char *format, ...) Like scanf, except input is taken
from the file fp.
int fprintf (FILE *fp, char *format, ...) Like printf, except output is written
to the file fp.
char *fgets (char *line, int n, FILE *fp) Gets the next line of input from the
file fp, up to `n-1' characters in length.
The newline character is included at the end
of the string, and a null is appended. Returns
`line' if successful, else NULL if end-of-file
or other error.
int fputs (char *line, FILE *fp) Outputs the string `line' to the file fp.
Returns zero is successful, EOF if not.
int fclose (FILE *fp) Closes the file 'fp', after flushing any
buffers. This function is automatically called
for any open files by the operating
system at the end of a program.
int fflush (FILE *fp) Flushes any buffers associated with the
file 'fp'.
int feof (FILE *fp) Returns non-zero when an end-of-file is read.
int ferror (FILE *fp) Returns non-zero when an error has occurred,
unless cleared by clearerr.
void clearerr (FILE *fp) Resets the error and end-of-file statuses.
int fileno (FILE *fp) Returns the integer file descriptor associated
with the file (useful for low-level I/O).
Structures
struct time {
int hour;
int minute;
float second;
};
struct time t1[10], t2 = {12, 0, 0.0F};
t1[0].hour = 12;
t1[0].minutes = 0;
t1[0].second = t2.second;
t1[1] = t2;
struct person {
char *name[80];
char *address[256];
struct person *next_person;
};
Multiple precision arithmetic
MP_INT x, y;
mpz_init (&x);
mpz_init (&y);
/* operations on x and y */
mpz_clear (&x);
mpz_clear (&y);
#include
cc -o two two.c -I/usr/local/include -L/usr/local/lib -lgmp
./two
PREPROCESSOR
#define PI 3.1415926535
#define SQR(a) (sqrarg=(a),sqrarg*sqrarg)
#include "filename" /* from the current directory */
#include
COMMAND-LINE ARGS, and returning a status to the shell
#include
echo $status
MALLOC:
#include
ASSIGNMENTS:
Everyone should attempt the following assignment:
(1) Write a program that reads STDIN and outputs each line in reverse
order to STDOUT, i.e., given the input
this is a test
of the program
tset a si siht
margorp eht fo
(2) Write a program that reads STDIN and replaces multiple consecutive
blanks with single blanks and removes trailing blanks, i.e., given the input
this is a test
of the program
this is a test
of the program
(3) Write a program that reads STDIN and returns the frequency of occurence
of the lengths of words (delimited by spaces or end-of-line characters),
i.e., given the input
this is a test
of the program
1 2 1 2 0 0 1
(4) Write a program that reads STDIN and returns the frequency of occurence
of all the ASCII characters in the input.
(5) Write a program that takes two command-line arguments: a token, and
a filename, and then prints out all lines in the file that contain
the token.
(6) Write a program that takes two command-line arguments: a token, and
a filename, and then prints out all lines in the file that don't contain
the token, regardless of case.
(7) Write a program that reads STDIN and prints out every token (delimited
by spaces or an end-of-line character) that is a valid integer.
(8) Write a program that reads STDIN and prints out every token (delimited
by spaces or an end-of-line character) that begins with a capital letter
and that only contains characters from the set A-Za-z.
(9) Write a program that reads an arbitrary number of filenames from the
command line, and write out the number of bytes in each of the files.
If a file doesn't exist, give a warning message.
The following examples are taken from CORONADO ENTERPRISES C
TUTOR - Ver 2.00
C programming course, School of Physics, UNSW / Michael Ashley / mcba@newt.phys.unsw.edu.au